package com.gitee.wsl.transform.validator

import arrow.core.EitherNel
import arrow.core.flatMap
import arrow.core.right
import com.gitee.wsl.func.InvokeCall
import com.gitee.wsl.transform.validator.func.notNull
import com.gitee.wsl.transform.validator.strings.notNullOrBlank

fun interface ValidatorWithTransform<I,out O,out E>: InvokeCall<I, EitherNel<E, O>> {

    fun validate(para: I): EitherNel<E, O>

    override operator fun invoke(t: I) = validate(t)

    fun <J> contramap(f: (J) -> I): ValidatorWithTransform<J, O, E> =
        ValidatorWithTransform { validate(f(it)) }

    operator fun <T> T.invoke(block: T.() -> Unit): T {
        block()
        return this
    }

    companion object{

        /**
         * Synonym for invoke.
         */
        fun <I> from() = invoke<I>()

        /**
         * Returns an identity [ParserValidatorWithError] for a type I.
         *
         * This can be used as the entry point to building a parser. Specify the source input type as
         * the input type parameter to this function, and a parser (I) -> EitherNel<Nothing, I> will
         * be returned.
         *
         * Eg:
         *
         * ValidatorWithError<String>() will return an identity parser that simply returns any input string.
         */
        operator fun <I> invoke(): ValidatorWithTransform<I, I, Nothing> = ValidatorWithTransform { it.right() }

        /**
         * An identity [ValidatorWithTransform] for nullable strings.
         */
        val nullableString: ValidatorWithTransform<String?, String?, Nothing> = ValidatorWithTransform()

        /**
         * An identity [ValidatorWithTransform] for nullable ints.
         */
        val nullableInt: ValidatorWithTransform<Int?, Int?, Nothing> = ValidatorWithTransform()

        /**
         * An identity [ValidatorWithTransform] for nullable longs.
         */
        val nullableLong: ValidatorWithTransform<Long?, Long?, Nothing> = ValidatorWithTransform()

        /**
         * An identity [ValidatorWithTransform] for nullable doubles.
         */
        val nullableDouble: ValidatorWithTransform<Double?, Double?, Nothing> = ValidatorWithTransform()

        /**
         * An identity [ValidatorWithTransform] for nullable floats.
         */
        val nullableFloat: ValidatorWithTransform<Double?, Double?, Nothing> = ValidatorWithTransform()

        fun <K, V> mapOf() = ValidatorWithTransform<Map<K, V>>()

        /**
         * A [ValidatorWithTransform] for nullable types, which returns the input if not null, or an error if null.
         * The error is derived from the given [errorFn] function.
         */
        fun <I, E> notNull(errorFn: () -> E): ValidatorWithTransform<I?, I, E> = ValidatorWithTransform<I?>().notNull { errorFn() }

        /**
         * A [ValidatorWithTransform] for nullable Strings, which returns the input if not blank, or an error if null or blank.
         * The error is derived from the given [errorFn] function.
         */
        fun <E> nonBlankString(errorFn: () -> E): ValidatorWithTransform<String?, String, E> =
                   ValidatorWithTransform<String?>().notNullOrBlank { errorFn() }


        fun nonBlankString(errorMessage:String): ValidatorWithTransform<String?, String, ValidationError> =
            ValidatorWithTransform<String?>().notNullOrBlank { ValidationError.Empty(errorMessage) }

    }
}

/**
 * Returns a [ValidatorWithTransform] that maps the result of this parser by invoking the given function [f]
 * and flattening the output of that function.
 *
 * @param f the function invoked to map the output of the underlying parser.
 *
 * @return a parser which returns the modified and flattened result of this parser.
 */
fun <I, O, E : E2, O2, E2> ValidatorWithTransform<I, O, E>.transformEither(f: (O) -> EitherNel<E2, O2>): ValidatorWithTransform<I, O2, E2> =
    ValidatorWithTransform { this@transformEither.validate(it).flatMap(f) }


fun <T, O, E> T.validateUse(rules: ValidatorWithTransform<T, O, E>) = rules.validate(this)

